﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using StackExchange.Redis;

namespace RedisDemo.Services
{
    public class CounterService
    {
        private readonly RedisManager _redisManager;

        public CounterService(RedisManager redisManager)
        {
            _redisManager = redisManager;
        }

        public void UpdateCounter(string name, int count = 1)
        {
            var now = DateTime.Now.ToUnixTimestamp();

            var db = _redisManager.GetDatabase();
            var pipe = db.CreateBatch();

            var precisions = new[] { 1, 5, 60, 300, 3600, 18000, 86400 };
            foreach (var prec in precisions)
            {
                var pnow = Convert.ToInt32(now / prec) * prec;
                var hash = $"{prec}:{name}";

                var task1 = pipe.SortedSetAddAsync("konwn:", hash, 0);
                var task2 = pipe.HashIncrementAsync($"count:{hash}", pnow, count);
            }

            pipe.Execute();
        }

        public async Task<Dictionary<int, int>> GetCounter(string name, int precision)
        {
            var hash = $"{precision}:{name}";

            var db = _redisManager.GetDatabase();

            var data = await db.HashGetAllAsync($"count:{hash}");

            var counter = new Dictionary<int, int>();
            foreach (var item in data)
            {
                counter.Add((int)item.Name, (int)item.Value);
            }

            return counter;
        }

        public async Task CleanCounters()
        {
            var db = _redisManager.GetDatabase();

            int passes = 0;
            while (true)
            {
                var start = DateTime.Now;
                var key = "konwn:";

                int index = 0;
                while (index < await db.SortedSetLengthAsync(key))
                {
                    var hashs = await db.SortedSetRangeByRankAsync(key, index, index);
                    index++;

                    if (hashs.Length <= 0) break;

                    var hash = hashs.FirstOrDefault();

                    var prec = Convert.ToInt32(hash.ToString().Split(":")[0]);
                    var bprec = Math.Max(Convert.ToInt32(prec / 60), 1);
                    if (passes % bprec != 0) continue;

                    var hkey = $"count:{hash}";

                    var coutoff = DateTime.Now.ToUnixTimestamp() - 120 * prec;
                    var keys = await db.HashKeysAsync(hkey);
                    var removeKeys = keys.Where(k => Convert.ToInt32(k) < coutoff);
                    if (removeKeys.Count() > 0)
                    {
                        await db.HashDeleteAsync(hkey, removeKeys.ToArray());
                    }
                    if (removeKeys.Count() == keys.Length)
                    {
                        var tran = db.CreateTransaction();
                        tran.AddCondition(Condition.HashLengthEqual(hkey, 0));

                        var task = tran.SortedSetRemoveAsync(key, hash);
                        await tran.ExecuteAsync();

                        index--;
                    }
                }

                passes++;
                var duration = Math.Min((DateTime.Now - start).TotalSeconds, 60);
                await Task.Delay(TimeSpan.FromSeconds(Math.Max(60 - duration, 1)));

                break;
            }
        }
    }
}
